#!/usr/bin/perl

######################################################################
package Swignition::GenericParser::RDF;
######################################################################

use Swignition::GenericParser::Utils;
use Swignition::MagicString;
use HTTP::Request;
use RDF::Redland;
use XML::LibXML qw(:all);
use XML::LibXSLT;
use URI::URL;

sub bnode_to_uri
{
	my $bnode = shift;
	my $store = shift;
	
	return $bnode->uri->as_string
		if ($bnode->is_resource);

	if (!defined $store->{ $bnode->blank_identifier })
	{
		my $pfx = $bnode->blank_identifier;
		$pfx = s/\d+$//;
		$store->{ $bnode->blank_identifier } = Swignition::GenericParser::Utils::BNodeURI(undef, $pfx);
	}
	
	return $store->{ $bnode->blank_identifier };
}

sub handle_trix
{
	my $this = shift;
	my $rdf  = shift;
	my $base = shift || $this->{RESPONSE}->base;
	my $type = shift || 'trix';
	my $grph = shift || 'RDF';
	my $DOM = shift;
	my $XMLParser = XML::LibXML->new();

	unless ($DOM)
	{
		$DOM = $XMLParser->parse_string($rdf);
	}
	
	# XSLT stuff
	foreach my $pi ( $DOM->findnodes('//processing-instruction()') )
	{
		next unless lc($pi->nodeName) eq  "xml-stylesheet";
		if ($pi->getData =~ /href="([^"]+)"/i)
		{
			my $uri = URI->new( $this->uri($1) );
			next if ($this->{NoFollow}->{ $uri->host });

			my $request  = HTTP::Request->new(GET => "$uri");
			$request->header('Accept', 'application/xslt+xml, '
											 . 'text/xslt, '
											 . 'text/xsl'
											 . 'application/xml, '
											 . 'text/xml');
			my $response = $this->get_ua->request($request);

			if ($response->header('content-type') =~ /(xsl|xml)/i)
			{
				my $xslt       = XML::LibXSLT->new();
				my $style_doc  = $XMLParser->parse_string($response->content);
	
				my $stylesheet;
				eval { $stylesheet = $xslt->parse_stylesheet($style_doc); };
								
				unless ($@)
				{
					my $results = $stylesheet->transform($DOM);
					$DOM = $results if ($results);
				}
			}
		}
	}
	
	# For each graph
	my $graphCount = 0;
	my $graphs = {};
	my $bnodes = {};
	foreach my $graph ($DOM->getElementsByTagNameNS('http://www.w3.org/2004/03/trix/trix-1/', 'graph'))	
	{
		$graphCount++;
		
		my $graphURI = undef;
		{
			my @uris  = $graph->getElementsByTagNameNS('http://www.w3.org/2004/03/trix/trix-1/', 'uri');
			$graphURI = $uris[0]->textContent;
			$graphURI = Swignition::GenericParser::Utils::BNodeURI(undef, sprintf("TriX%02d_GRAPH", $graphCount))
				unless ($graphURI);
		}
		
		foreach my $triple ($graph->getElementsByTagNameNS('http://www.w3.org/2004/03/trix/trix-1/', 'triple'))
		{
			my @components = $triple->getChildrenByTagName('*');
			
			next if defined $components[3]; # Only [0] to [2] should be defined.
			next unless defined $components[2]; # But [0] to [2] must be defined!
			
			next unless $components[0]->tagName =~ /^(id|uri)$/; 
			next unless $components[1]->tagName eq 'uri';
			next unless $components[2]->tagName =~ /^(id|uri|plainLiteral|typedLiteral)$/; 
			
			my @uris;
			for (my $i=0 ; $i<3; $i++)
			{
				if ($components[$i]->tagName eq 'uri')
				{
					$uris[$i] = $components[$i]->textContent;
				}
				elsif ($components[$i]->tagName eq 'id')
				{
					# Note that blank nodes are not shared between graphs. <id>a</id> in
					# two different graphs are treated as two different blank nodes!
					my $preferredID = $components[$i]->textContent;
					$bnodes->{$graphURI}->{$preferredID} = Swignition::GenericParser::Utils::BNodeURI(undef, $preferredID)
						unless defined $bnodes->{$graphURI}->{$preferredID};
					$uris[$i] = $bnodes->{$graphURI}->{$preferredID};
				}
			}
			
			$graphs->{$graphURI}->{ $uris[0] } = Swignition::RDFModel::Subject->new($uris[0])
				unless defined $graphs->{$graphURI}->{ $uris[0] };

			my $value;
			
			if ($components[2]->tagName eq 'plainLiteral')
			{
				my $ms = $components[2]->textContent;
				if ($components[2]->getAttribute('xml:lang'))
					{ $ms = strlang($ms, $components[2]->getAttribute('xml:lang')); }
				$value = Swignition::RDFModel::Value->new($ms);
			}
			elsif ($components[2]->tagName eq 'typedLiteral')
			{
				if ($components[2]->getAttribute('datatype') eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral'
				&&  $components[2]->getElementsByTagName('*'))
				{
					my $xml = '';
					foreach my $kid ($components[2]->childNodes)
						{ $xml .= $kid->toStringC14N; }
					$value = Swignition::RDFModel::Value->new($xml);
				}
				else
				{
					$value = Swignition::RDFModel::Value->new($components[2]->textContent);
				}
				$value->setDatatype($components[2]->getAttribute('datatype'));
			}
			elsif (defined $uri[2])
			{
				$value = Swignition::RDFModel::Value->new($uris[2], 1);
			}
			
			$graphs->{$graphURI}->{ $uris[0] }->addVal($uris[1], $value);
		}
		
		foreach my $subject (keys %{ $graphs->{$graphURI} })
		{
			$this->mergeSubject( $graphs->{$graphURI}->{$subject} );
		}
	}
}

sub handle
{
	my $this = shift;
	my $rdf  = shift;
	my $base = shift || $this->{RESPONSE}->base;
	my $type = shift || 'rdfxml';
	my $grph = shift || 'RDF';
	
	# TriX isn't supported by Redland.
	if ($type eq 'trix')
	{
		handle_trix($this, $rdf, $base, $type, $grph);
	}
	
	my $baseobj = new RDF::Redland::URI("$base");
	my $parser  = new RDF::Redland::Parser($type);
	my $stream;
	
	if (!eval{$stream = $parser->parse_string_as_stream($rdf, $baseobj);})
	{
		# Funny little way of outputting an error message.
		my $S = Swignition::RDFModel::Subject->new($base);
		my $V = Swignition::RDFModel::Value->new("Could not parse as $type.");
		$S->addVal('http://www.w3.org/2000/01/rdf-schema#comment', $V);
		$this->mergeSubject($S);
		return;
	}

	my $bnode_uris = {};

	while ($stream && !$stream->end)	
	{
		my $statement = $stream->current;
		
		my $subj = $statement->subject;
		my $prop = $statement->predicate;
		my $obj  = $statement->object;
		
		next if ($subj->is_literal || $prop->is_literal);
		
		my $subj_uri = bnode_to_uri($subj, $bnode_uris);
		my $prop_uri = bnode_to_uri($prop, $bnode_uris);
		
		my $S = Swignition::RDFModel::Subject->new($subj_uri);
		my $P = $prop_uri;
		my $V;
		
		if ($obj->is_literal)
		{
			$V = Swignition::RDFModel::Value->new($obj->literal_value);
			$V->setDatatype($obj->literal_datatype->as_string)
				if ($obj->literal_datatype);
		}
		else
		{
			$V = Swignition::RDFModel::Value->new(bnode_to_uri($obj, $bnode_uris), 1);
		}

		$S->addVal($P, $V);				
		$this->mergeSubject($S, $grph);	
		
		$stream->next;
	}
	
	if ($type eq 'n3' || $type eq 'turtle')
	{
		while ($rdf =~ / \@prefix \s+ ([A-Za-z0-9_-]*) \s* \: \s* <([^>]+)> \s* \. /gx)
		{
			my $nsurl  = $2;
			my $prefix = length($1) ? $1 : 'p1';
			$this->mergeNS($prefix, $nsurl, "RDF $prefix", -1, 0);
		}
	}
	elsif ($RDF::Redland::VERSION gt '1.00_06_00')
	{
		my %ns = $parser->namespaces_seen;
		foreach my $k (keys %ns)
		{
			$this->mergeNS($k, $ns{$k}->as_string, "RDF $k", -1, 0);
		}
	}
	elsif ($type eq 'rdfxml')
	{
		while ($rdf =~ /xmlns\:?([A-Za-z0-9_-]*)\=\"([^"]*)\"/g)
		{
			my $nsurl  = $2;
			my $prefix = length($1) ? $1 : 'p1';
			$this->mergeNS($prefix, $nsurl, "RDF $prefix", -1, 0);
		}
		while ($rdf =~ /xmlns\:?([A-Za-z0-9_-]*)\=\'([^']*)\'/g)
		{
			my $nsurl  = $2;
			my $prefix = length($1) ? $1 : 'p1';
			$this->mergeNS($prefix, $nsurl, "RDF $prefix", -1, 0);
		}
	}
} #/sub handle_rdf

1;


######################################################################
package Swignition::GenericParser::RDF::Comment;
######################################################################

sub is_enabled
{
	my $this = shift;
	return $this->{options}->{p_comments};
}

sub consume
{
	my $this = shift;
	my $src = $this->{_raw_source} || $this->{Source};

	while ($src =~ /<!--\s*(.*)\s*-->/sg)
	{
		my $comment = $1;
		Swignition::GenericParser::RDF::handle($this, $comment)
			if ($comment =~ /^[\s\r\n]*<rdf:RDF/);
	}
}

1;


######################################################################
package Swignition::GenericParser::RDF::Embedded;
######################################################################

sub is_enabled
{
	my $this = shift;
	return $this->{options}->{p_rdf};
}

sub consume
{
	my $this = shift;
	my @rdfs = $this->{DOM}->getElementsByTagNameNS("http://www.w3.org/1999/02/22-rdf-syntax-ns#", 'RDF');
	foreach my $rdf (@rdfs)
		{ Swignition::GenericParser::RDF::handle($this, $rdf->toString); }
} #/sub consume

1;

